package com.android.launcher3;

import java.lang.ref.WeakReference;
import java.net.URISyntaxException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;

/**
 * Maintains in-memory state of the Launcher. It is expected that there should
 * be only one LauncherModel object held in a static. Also provide APIs for
 * updating the database state for the Launcher.
 */
public class LauncherModel extends BroadcastReceiver {

	static final boolean DEBUG_LOADERS = true;
	static final String LOG_TAG = "HomeLoaders";
	public static final String TAG = "LauncherModel ";

	public static final String[] APPS_PROJECTION = new String[] {
			LauncherSettings.Apps._ID, LauncherSettings.Apps.COMPONENT_NAME,
			LauncherSettings.Apps.TOP_NUMBER,
			LauncherSettings.Apps.PAGE_NUMBER,
			LauncherSettings.Apps.CELL_NUMBER };

	private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
	private static final Collator sCollator = Collator.getInstance();

	private final HashMap<ComponentName, ApplicationInfo> mAppInfoCache = new HashMap<ComponentName, ApplicationInfo>(
			INITIAL_ICON_CACHE_CAPACITY);

	private final Object mLock;
	private DeferredHandler mHandler;
	private android.os.Handler osHandler = new android.os.Handler();
	private WeakReference<Callbacks> mCallbacks;
	private AllAppsList mAllAppsList;
	private boolean mBeforeFirstLoad;
	private boolean mBeforeFirstQuery;
	private IconCache mIconCache;
	private boolean mAllAppsLoaded;
	private Loader mLoader;
	private boolean mWorkspaceLoaded;
	private Bitmap mDefaultIcon;
	private boolean mIsBusy;
	private final Object mAllAppsListLock;
	private final LauncherApplication mApp;
	private int mAllAppsLoadDelay;
	private int mBatchSize;
	private Launcher mLauncher;
	private int querySdCardAppTimes;
	private Runnable querySdCardAppRunnable;

	/**
	 * 
	 * @param Application
	 * @param iconCache
	 */
	LauncherModel(LauncherApplication application, IconCache iconCache) {
		this.mLock = new Object();
		this.mHandler = new DeferredHandler();
		this.mLoader = new Loader();
		this.mBeforeFirstLoad = true;
		this.mBeforeFirstQuery = true;
		this.mAllAppsListLock = new Object();
		this.mIsBusy = false;
		this.mApp = application;
		this.mAllAppsList = new AllAppsList(iconCache);
		this.mIconCache = iconCache;
		this.mDefaultIcon = Utilities.createIconBitmap(application
				.getPackageManager().getDefaultActivityIcon(), application,
				null);

		// this.mAllAppsLoadDelay = 0;
		// this.mBatchSize = 0;

		mAllAppsLoadDelay = application.getResources().getInteger(
				R.integer.config_allAppsBatchLoadDelay);
		mBatchSize = application.getResources().getInteger(
				R.integer.config_allAppsBatchSize);

	}

	/**
	 * Drop our cache of components to their lables & icons. We do this from
	 * Launcher when applications are added/removed. It's a bit overkill, but
	 * it's a rare operation anyway.
	 */
	synchronized void dropApplicationCache() {
		mAppInfoCache.clear();
	}

	/**
	 * 
	 * @author hehui
	 * 
	 */
	static class ApplicationInfoComparator implements
			Comparator<ApplicationInfo> {
		private int mMode;

		ApplicationInfoComparator() {
		}

		ApplicationInfoComparator(int mode) {
			this.mMode = mode;
		}

		private int integerCompare(int paramInt1, int paramInt2) {
			if (paramInt1 > paramInt2) {
				return 1;
			}
			if (paramInt1 == paramInt2) {
				return 0;
			}
			return -1;
		}

		public final int compare(ApplicationInfo paramApplicationInfo1,
				ApplicationInfo paramApplicationInfo2) {
			Collator localCollator;
			String str1;
			String str2;
			int k = 0;
			if (this.mMode == 2) {
				int i = paramApplicationInfo1.editTopNum;
				int j = paramApplicationInfo2.editTopNum;
				k = integerCompare(i, j);
				if (k == 0) {
					int m = paramApplicationInfo1.editPageNum;
					int n = paramApplicationInfo2.editPageNum;
					k = integerCompare(m, n);
					if (k == 0) {
						int i1 = paramApplicationInfo1.editCellNum;
						int i2 = paramApplicationInfo2.editCellNum;
						k = integerCompare(i1, i2);
					}
				}
				if (k == 0) {
					localCollator = LauncherModel.sCollator;
					str1 = paramApplicationInfo1.title.toString();
					str2 = paramApplicationInfo2.title.toString();
					k = localCollator.compare(str1, str2);
				}
				return k;
			} else {
				int i = paramApplicationInfo1.topNum;
				int j = paramApplicationInfo2.topNum;
				k = integerCompare(i, j);
				if (k == 0) {
					int m = paramApplicationInfo1.pageNum;
					int n = paramApplicationInfo2.pageNum;
					k = integerCompare(m, n);
					if (k == 0) {
						int i1 = paramApplicationInfo1.cellNum;
						int i2 = paramApplicationInfo2.cellNum;
						k = integerCompare(i1, i2);
					}
				}
				if (k == 0) {
					localCollator = LauncherModel.sCollator;
					str1 = paramApplicationInfo1.title.toString();
					str2 = paramApplicationInfo2.title.toString();
					k = localCollator.compare(str1, str2);
				}
				return k;

			}
		}

		public void setMode(int paramInt) {
			this.mMode = paramInt;
		}
	}

	/**
	 * 
	 * @param manager
	 * @param activityInfo
	 * @return
	 */
	private static String getLabel(PackageManager manager,
			ActivityInfo activityInfo) {
		String label = activityInfo.loadLabel(manager).toString();
		if (label == null) {
			label = manager.getApplicationLabel(activityInfo.applicationInfo)
					.toString();
			if (label == null) {
				label = activityInfo.name;
			}
		}
		return label;
	}

	private static void loadLiveFolderIcon(Context paramContext, Cursor c,
			int iconTypeIndex, int iconPackageIndex, int iconResourceIndex,
			LiveFolderInfo liveFolderInfo) {

		int iconType = c.getInt(iconTypeIndex);
		switch (iconType) {
		case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
			String packageName = c.getString(iconPackageIndex);
			String resourceName = c.getString(iconResourceIndex);
			PackageManager packageManager = paramContext.getPackageManager();
			try {
				Resources resources = packageManager
						.getResourcesForApplication(packageName);
				final int id = resources
						.getIdentifier(resourceName, null, null);
				liveFolderInfo.icon = Utilities.createIconBitmap(
						resources.getDrawable(id), paramContext, null);
			} catch (Exception e) {
				// liveFolderInfo.icon =
				// paramContext.getResources().getDrawable(
				// R.drawable.ic_launcher_folder);
				liveFolderInfo.icon = Utilities.createIconBitmap(
						paramContext.getResources().getDrawable(
								R.drawable.ic_launcher_folder), paramContext,
						null);
			}
			liveFolderInfo.iconResource = new Intent.ShortcutIconResource();
			liveFolderInfo.iconResource.packageName = packageName;
			liveFolderInfo.iconResource.resourceName = resourceName;
			break;
		default:
			// liveFolderInfo.icon = paramContext.getResources().getDrawable(
			// R.drawable.ic_launcher_folder);
			liveFolderInfo.icon = Utilities.createIconBitmap(paramContext
					.getResources().getDrawable(R.drawable.ic_launcher_folder),
					paramContext, null);
		}
	}

	/**
	 * Return an existing UserFolderInfo object if we have encountered this ID
	 * previously, or make a new one.
	 */
	private UserFolderInfo findOrMakeUserFolder(
			HashMap<Long, FolderInfo> folders, long id) {
		// See if a placeholder was created for us already
		FolderInfo folderInfo = folders.get(id);
		if (folderInfo == null || !(folderInfo instanceof UserFolderInfo)) {
			// No placeholder -- create a new instance
			folderInfo = new UserFolderInfo();
			folders.put(id, folderInfo);
		}
		return (UserFolderInfo) folderInfo;
	}

	/**
	 * Return an existing UserFolderInfo object if we have encountered this ID
	 * previously, or make a new one.
	 */
	private LiveFolderInfo findOrMakeLiveFolder(
			HashMap<Long, FolderInfo> folders, long id) {
		// See if a placeholder was created for us already
		FolderInfo folderInfo = folders.get(id);
		if (folderInfo == null || !(folderInfo instanceof LiveFolderInfo)) {
			// No placeholder -- create a new instance
			folderInfo = new LiveFolderInfo();
			folders.put(id, folderInfo);
		}
		return (LiveFolderInfo) folderInfo;
	}

	ShortcutInfo addShortcut(Context context, Intent paramIntent,
			CellLayout.CellInfo paramCellInfo, boolean notify) {
		ShortcutInfo localShortcutInfo = infoFromShortcutIntent(context,
				paramIntent);
		int i = paramCellInfo.screen;
		int j = paramCellInfo.cellX;
		int k = paramCellInfo.cellY;
		addItemToDatabase(context, localShortcutInfo,
				LauncherSettings.Favorites.CONTAINER_DESKTOP, i, j, k, notify);
		return localShortcutInfo;
	}

	/**
	 * 
	 * @param context
	 * @param data
	 * @return
	 */
	private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
		Log.e(TAG, "infoFromShortcutIntent");
		Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
		String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
		Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);

		Bitmap icon = null;
		boolean filtered = false;
		boolean customIcon = false;
		ShortcutIconResource iconResource = null;

		if (bitmap != null && bitmap instanceof Bitmap) {
			icon = Utilities.createIconBitmap(new FastBitmapDrawable(
					(Bitmap) bitmap), context, null);
			filtered = true;
			customIcon = true;
		} else {
			Parcelable extra = data
					.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
			iconResource = (ShortcutIconResource) extra;
			ComponentName cp = null;

			if (extra != null && extra instanceof ShortcutIconResource) {
				try {
					String packageName = iconResource.packageName;
					cp = new ComponentName(packageName, "");

					final PackageManager packageManager = context
							.getPackageManager();
					Resources resources = packageManager
							.getResourcesForApplication(iconResource.packageName);
					final int id = resources.getIdentifier(
							iconResource.resourceName, null, null);
					icon = Utilities.createIconBitmap(
							resources.getDrawable(id), context, cp);
				} catch (Exception e) {
					Log.w(TAG, "Could not load shortcut icon: " + extra);
				}
			}
		}

		final ShortcutInfo info = new ShortcutInfo();

		if (icon == null) {
			icon = getFallbackIcon();
			info.usingFallbackIcon = true;
		}
		info.setIcon(icon);

		info.title = name;
		info.intent = intent;
		info.customIcon = customIcon;
		info.iconResource = iconResource;

		return info;
	}

	/**
	 * 
	 * @return
	 */
	public Bitmap getFallbackIcon() {
		return Bitmap.createBitmap(this.mDefaultIcon);
	}

	/**
	 * This is called from the code that adds shortcuts from the intent
	 * receiver. This doesn't have a Cursor, but
	 */
	public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
			Context context) {
		return getShortcutInfo(manager, intent, context, null, -1, -1);
	}

	/**
	 * Make an ShortcutInfo object for a shortcut that is an application.
	 * 
	 * If c is not null, then it will be used to fill in missing data like the
	 * title and icon.
	 */
	public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
			Context context, Cursor c, int iconIndex, int titleIndex) {
		Bitmap icon = null;
		final ShortcutInfo info = new ShortcutInfo();

		ComponentName componentName = intent.getComponent();
		if (componentName == null) {
			return null;
		}

		// TODO: See if the PackageManager knows about this case. If it doesn't
		// then return null & delete this.

		// the resource -- This may implicitly give us back the fallback icon,
		// but don't worry about that. All we're doing with usingFallbackIcon is
		// to avoid saving lots of copies of that in the database, and most apps
		// have icons anyway.
		final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
		if (resolveInfo != null) {
			icon = mIconCache.getIcon(componentName, resolveInfo);
		}
		// the db
		if (icon == null) {
			if (c != null) {
				icon = getIconFromCursor(c, iconIndex);
			}
		}
		// the fallback icon
		if (icon == null) {
			icon = getFallbackIcon();
			info.usingFallbackIcon = true;
		}
		info.setIcon(icon);

		// from the resource
		if (resolveInfo != null) {
			info.title = resolveInfo.activityInfo.loadLabel(manager);
		}
		// from the db
		if (info.title == null) {
			if (c != null) {
				info.title = c.getString(titleIndex);
			}
		}
		// fall back to the class name of the activity
		if (info.title == null) {
			info.title = componentName.getClassName();
		}
		info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
		return info;
	}

	/**
	 * Make an ShortcutInfo object for a shortcut that isn't an application.
	 */
	private ShortcutInfo getShortcutInfo(Cursor c, Context context,
			int iconTypeIndex, int iconPackageIndex, int iconResourceIndex,
			int iconIndex, int titleIndex) {
		Bitmap icon = null;
		final ShortcutInfo info = new ShortcutInfo();
		info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;

		// TODO: If there's an explicit component and we can't install that,
		// delete it.

		info.title = c.getString(titleIndex);

		int iconType = c.getInt(iconTypeIndex);
		switch (iconType) {
		case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
			String packageName = c.getString(iconPackageIndex);
			String resourceName = c.getString(iconResourceIndex);
			PackageManager packageManager = context.getPackageManager();
			info.customIcon = false;
			// the resource
			try {
				Resources resources = packageManager
						.getResourcesForApplication(packageName);
				if (resources != null) {
					final int id = resources.getIdentifier(resourceName, null,
							null);
					icon = Utilities.createIconBitmap(
							resources.getDrawable(id), context, null);
				}
			} catch (Exception e) {
				// drop this. we have other places to look for icons
			}
			// the db
			if (icon == null) {
				icon = getIconFromCursor(c, iconIndex);
			}
			// the fallback icon
			if (icon == null) {
				icon = getFallbackIcon();
				info.usingFallbackIcon = true;
			}
			break;
		case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
			icon = getIconFromCursor(c, iconIndex);
			if (icon == null) {
				icon = getFallbackIcon();
				info.customIcon = false;
				info.usingFallbackIcon = true;
			} else {
				info.customIcon = true;
			}
			break;
		default:
			icon = getFallbackIcon();
			info.usingFallbackIcon = true;
			info.customIcon = false;
			break;
		}
		info.setIcon(icon);
		return info;

	}

	/**
	 * 
	 * @param cursor
	 * @param columnIndex
	 * @return
	 */
	Bitmap getIconFromCursor(Cursor cursor, int columnIndex) {
		byte[] arrayOfByte = cursor.getBlob(columnIndex);
		try {
			int i = arrayOfByte.length;
			Bitmap localBitmap1 = BitmapFactory.decodeByteArray(arrayOfByte, 0,
					i);
			return localBitmap1;
		} catch (Exception localException) {
		}
		return null;
	}

	/**
	 * Adds an item to the DB if it was not created previously, or move it to a
	 * new <container, screen, cellX, cellY>
	 */
	static void addOrMoveItemInDatabase(Context context, ItemInfo item,
			long container, int screen, int cellX, int cellY) {
		if (item.container == ItemInfo.NO_ID) {
			// From all apps
			addItemToDatabase(context, item, container, screen, cellX, cellY,
					false);
		} else {
			// From somewhere else
			moveItemInDatabase(context, item, container, screen, cellX, cellY);
		}
	}

	/**
	 * Move an item in the DB to a new <container, screen, cellX, cellY>
	 */
	static void moveItemInDatabase(Context context, ItemInfo item,
			long container, int screen, int cellX, int cellY) {
		item.container = container;
		item.screen = screen;
		item.cellX = cellX;
		item.cellY = cellY;
		final ContentValues values = new ContentValues();
		final ContentResolver cr = context.getContentResolver();
		values.put(LauncherSettings.Favorites.CONTAINER, item.container);
		values.put(LauncherSettings.Favorites.CELLX, item.cellX);
		values.put(LauncherSettings.Favorites.CELLY, item.cellY);
		values.put(LauncherSettings.Favorites.SCREEN, item.screen);
		cr.update(LauncherSettings.Favorites.getContentUri(item.id, false),
				values, null, null);
	}

	/**
	 * Returns true if the shortcuts already exists in the database. we identify
	 * a shortcut by its title and intent.
	 */
	static boolean shortcutExists(Context context, String title, Intent intent) {
		final ContentResolver cr = context.getContentResolver();
		Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
				new String[] { "title", "intent" }, "title=? and intent=?",
				new String[] { title, intent.toUri(0) }, null);
		boolean result = false;
		try {
			result = c.moveToFirst();
		} finally {
			c.close();
		}
		return result;
	}

	/**
	 * Find a folder in the db, creating the FolderInfo if necessary, and adding
	 * it to folderList.
	 */
	FolderInfo getFolderById(Context context,
			HashMap<Long, FolderInfo> folderList, long id) {
		final ContentResolver cr = context.getContentResolver();
		Cursor c = cr
				.query(LauncherSettings.Favorites.CONTENT_URI,
						null,
						"_id=? and (itemType=? or itemType=?)",
						new String[] {
								String.valueOf(id),
								String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER),
								String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER) },
						null);

		try {
			if (c.moveToFirst()) {
				final int itemTypeIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
				final int titleIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
				final int containerIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
				final int screenIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
				final int cellXIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
				final int cellYIndex = c
						.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);

				FolderInfo folderInfo = null;
				switch (c.getInt(itemTypeIndex)) {
				case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
					folderInfo = findOrMakeUserFolder(folderList, id);
					break;
				case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
					folderInfo = findOrMakeLiveFolder(folderList, id);
					break;
				}

				folderInfo.title = c.getString(titleIndex);
				folderInfo.id = id;
				folderInfo.container = c.getInt(containerIndex);
				folderInfo.screen = c.getInt(screenIndex);
				folderInfo.cellX = c.getInt(cellXIndex);
				folderInfo.cellY = c.getInt(cellYIndex);

				return folderInfo;
			}
		} finally {
			c.close();
		}

		return null;
	}

	/**
	 * Add an item to the database in a specified container. Sets the container,
	 * screen, cellX and cellY fields of the item. Also assigns an ID to the
	 * item.
	 */
	static void addItemToDatabase(Context context, ItemInfo item,
			long container, int screen, int cellX, int cellY, boolean notify) {
		item.container = container;
		item.screen = screen;
		item.cellX = cellX;
		item.cellY = cellY;

		final ContentValues values = new ContentValues();
		final ContentResolver cr = context.getContentResolver();

		item.onAddToDatabase(values);

		Uri result = cr.insert(notify ? LauncherSettings.Favorites.CONTENT_URI
				: LauncherSettings.Favorites.CONTENT_URI_NO_NOTIFICATION,
				values);
		if (result != null) {
			item.id = Integer.parseInt(result.getPathSegments().get(1));
		}
	}

	/**
	 * Update an item to the database in a specified container.
	 */
	static void updateItemInDatabase(Context context, ItemInfo item) {
		final ContentValues values = new ContentValues();
		final ContentResolver cr = context.getContentResolver();
		item.onAddToDatabase(values);
		cr.update(LauncherSettings.Favorites.getContentUri(item.id, false),
				values, null, null);
	}

	/**
	 * Removes the specified item from the database
	 * 
	 * @param context
	 * @param item
	 */
	static void deleteItemFromDatabase(Context context, ItemInfo item) {
		final ContentResolver cr = context.getContentResolver();
		cr.delete(LauncherSettings.Favorites.getContentUri(item.id, false),
				null, null);
	}

	/**
	 * Remove the contents of the specified folder from the database
	 */
	static void deleteUserFolderContentsFromDatabase(Context context,
			UserFolderInfo info) {
		final ContentResolver cr = context.getContentResolver();

		cr.delete(LauncherSettings.Favorites.getContentUri(info.id, false),
				null, null);
		cr.delete(LauncherSettings.Favorites.CONTENT_URI,
				LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
	}

	/**
	 * 
	 * @param context
	 * @return
	 */
	public static Cursor loadTopAppFromDatabase(Context context) {
		LogUtils.d(TAG, "loadTopAppToDatabase");
		Cursor cursor = context.getContentResolver().query(
				LauncherSettings.Apps.CONTENT_URI, APPS_PROJECTION,
				LauncherSettings.Apps.TOP_NUMBER + " != -1", null,
				"top_number asc");
		return cursor;
	}

	/**
	 */
	void updateSavedIcon(Context context, ShortcutInfo info, Cursor c,
			int iconIndex) {
		// If this icon doesn't have a custom icon, check to see
		// what's stored in the DB, and if it doesn't match what
		// we're going to show, store what we are going to show back
		// into the DB. We do this so when we're loading, if the
		// package manager can't find an icon (for example because
		// the app is on SD) then we can use that instead.
		if (!info.customIcon && !info.usingFallbackIcon) {
			boolean needSave = false;
			// byte[] data = c.getBlob(iconIndex);
			// try {
			// if (data != null) {
			// Bitmap saved = BitmapFactory.decodeByteArray(data, 0,
			// data.length);
			// Bitmap loaded = info.getIcon(mIconCache);
			// // needSave = !saved.sameAs(loaded);
			// needSave = true;
			// } else {
			// needSave = true;
			// }
			// } catch (Exception e) {
			// needSave = true;
			// }
			if (needSave) {
				Log.d(TAG, "going to save icon bitmap for info=" + info);
				// This is slower than is ideal, but this only happens either
				// after the froyo OTA or when the app is updated with a new
				// icon.
				updateItemInDatabase(context, info);
			}
		}
	}

	/**
	 * 
	 * @param context
	 * @param applicationInfo
	 */
	static void addAppToDatabase(Context context,
			ApplicationInfo applicationInfo) {

		// Log.e(TAG,"addAppToDatabase ");

		Cursor c = context.getContentResolver().query(
				LauncherSettings.Apps.CONTENT_URI,
				new String[] { "count (*)" }, "top_number==-1", null, null);
		// DatabaseUtils.dumpCursor(c);
		c.moveToFirst();
		int count = c.getInt(0);
		c.close();

		ContentValues localContentValues = new ContentValues();
		ContentResolver localContentResolver = context.getContentResolver();
		ComponentName localComponentName = applicationInfo.intent
				.getComponent();
		Cursor appCursor = null;
		try {
			String[] appProjection = new String[1];
			appProjection[0] = "_id";
			StringBuilder appSelection = new StringBuilder()
					.append("componentname='");
			appSelection.append(localComponentName.flattenToString()).append(
					"'");
			appCursor = localContentResolver.query(
					LauncherSettings.Apps.CONTENT_URI, appProjection,
					appSelection.toString(), null, null);

			if (appCursor != null && appCursor.getCount() > 0) {
				return;
			}
			String strComponentname = localComponentName.flattenToString();

			localContentValues.put(LauncherSettings.Apps.COMPONENT_NAME,
					strComponentname);

			// localContentValues.put(LauncherSettings.Apps.CELL_NUMBER,
			// applicationInfo.cellNum);
			// localContentValues.put(LauncherSettings.Apps.PAGE_NUMBER,
			// applicationInfo.pageNum);
			// localContentValues.put(LauncherSettings.Apps.TOP_NUMBER,
			// applicationInfo.topNum);

			// 贺辉2011年10月31日11:37:27 修改

			localContentValues.put(LauncherSettings.Apps.CELL_NUMBER,
					(count) % 16);
			localContentValues.put(LauncherSettings.Apps.PAGE_NUMBER,
					(count) / 16);
			localContentValues.put(LauncherSettings.Apps.TOP_NUMBER, -1);

			applicationInfo.cellNum = (count) % 16;
			applicationInfo.pageNum = (count) / 16;
			applicationInfo.topNum = -1;

			applicationInfo.editCellNum = (count) % 16;

			applicationInfo.editPageNum = (count) / 16;

			applicationInfo.editTopNum = -1;

			localContentResolver.insert(LauncherSettings.Apps.CONTENT_URI,
					localContentValues);

			// Log.e(TAG,"addAppToDatabase  title "+applicationInfo.title);

			// Log.e(TAG,"addAppToDatabase  localContentValues "+localContentValues.toString());

			// Log.d("Launcher", "insert uri= " + localUri3);

		} catch (SQLiteException e) {
			e.printStackTrace();
		} catch (IllegalStateException e2) {
			e2.printStackTrace();
		} finally {
			if (appCursor != null) {
				appCursor.close();
			}
		}
	}

	/**
	 * 
	 * @param context
	 * @param applicationInfo
	 */
	static void removeAppToDatabase(Context context,
			ApplicationInfo applicationInfo) {
		if (context == null)
			return;
		ContentResolver localContentResolver = context.getContentResolver();
		StringBuilder localStringBuilder = new StringBuilder()
				.append("componentname='");
		localStringBuilder.append(
				applicationInfo.intent.getComponent().flattenToString())
				.append("'");
		localContentResolver.delete(LauncherSettings.Apps.CONTENT_URI,
				localStringBuilder.toString(), null);
	}

	/**
	 * 
	 * @param context
	 * @param id
	 */
	static void removeAppToDatabase(Context context, String id) {
		if (context == null)
			return;
		ContentResolver localContentResolver = context.getContentResolver();
		String str = "_id='" + id + "'";
		localContentResolver.delete(LauncherSettings.Apps.CONTENT_URI, str,
				null);
	}

	/**
	 * 
	 * @param context
	 * @param applicationInfo
	 * @return
	 */
	static int updateAppToDatabase(Context context,
			ApplicationInfo applicationInfo) {
		if (context == null)
			return -1;
		// Log.e(TAG,"updateAppToDatabase");
		if (applicationInfo.isUpdated) {
			return -1;
		}

		ContentValues localContentValues = new ContentValues();
		ContentResolver localContentResolver = context.getContentResolver();
		ComponentName componentName = applicationInfo.intent.getComponent();
		Cursor appCursor = null;

		try {
			String[] appProjection = new String[1];
			appProjection[0] = "_id";
			StringBuilder appSelection = new StringBuilder()
					.append("componentname='");
			appSelection.append(componentName.flattenToString()).append("'");
			appCursor = localContentResolver.query(
					LauncherSettings.Apps.CONTENT_URI, appProjection,
					appSelection.toString(), null, null);
			if (appCursor != null && appCursor.getCount() > 0) {
				appCursor.moveToFirst();
				int id = appCursor.getInt(0);
				if (id != -1) {
					localContentValues.put(LauncherSettings.Apps.TOP_NUMBER,
							applicationInfo.topNum);
					localContentValues.put(LauncherSettings.Apps.PAGE_NUMBER,
							applicationInfo.pageNum);
					localContentValues.put(LauncherSettings.Apps.CELL_NUMBER,
							applicationInfo.cellNum);
					applicationInfo.isUpdated = true;
					Uri uri = LauncherSettings.Apps.getContentUri(id);
					int updateCount = localContentResolver.update(uri,
							localContentValues, null, null);
					// Log.e(TAG,"updateAppToDatabase");
					return updateCount;
				}
			} else {
				// Log.e(TAG,"addAppToDatabase");
				addAppToDatabase(context, applicationInfo);
				return 1;
			}
		} catch (SQLiteException localSQLiteException) {
			Log.e("Launcher",
					"addAppToDatabase() " + localSQLiteException.getMessage());
		} catch (IllegalStateException localIllegalStateException) {
			Log.e("Launcher", "addAppToDatabase() "
					+ localIllegalStateException.getMessage());
		} finally {
			if (appCursor != null) {
				appCursor.close();
			}
		}

		return -1;
	}

	/**
	 * 
	 * @return
	 */
	boolean isBusy() {
		return this.mIsBusy;
	}

	/**
	 * 
	 * @param callbacks
	 */
	public void initialize(Callbacks callbacks) {
		synchronized (this.mLock) {
			this.mCallbacks = new WeakReference<Callbacks>(callbacks);
			this.mHandler.cancel();
			return;
		}
	}

	/**
	 * 
	 * @param context
	 * @param paramBoolean
	 */
	public void startLoader(Launcher context, boolean isLaunching) {
		synchronized (mLock) {
			Log.d("Launcher", TAG + "startLoader isLaunching=" + isLaunching);
			this.mWorkspaceLoaded = false;
			this.mAllAppsLoaded = false;
			this.mHandler.cancel();
			this.mLauncher = context;
			this.mLoader.startLoader(context, isLaunching);
		}
	}

	/**
	 * 
	 * @param context
	 * @param paramBoolean
	 */
	public void startLoader(Context context, boolean isLaunching) {
		synchronized (mLock) {
			Log.d("Launcher", TAG + "startLoader isLaunching=" + isLaunching);
			this.mWorkspaceLoaded = false;
			this.mAllAppsLoaded = false;
			this.mHandler.cancel();
			this.mLoader.startLoader(context, isLaunching);
		}
	}

	public abstract interface Callbacks {
		public void bindAllApplications(ArrayList<ApplicationInfo> apps);

		public void bindAppsAdded(ArrayList<ApplicationInfo> apps);

		public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);

		public abstract void bindAppsRemoved(ArrayList<ApplicationInfo> apps);

		public void bindAppWidget(LauncherAppWidgetInfo info);

		public void bindFolders(HashMap<Long, FolderInfo> folders);

		public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);

		public abstract void finishBindingAllWorkspaceItems();

		public abstract void finishBindingAppWidgets();

		public abstract void finishBindingSamsungWidgets();

		public abstract void finishBindingShortcuts();

		public abstract int getCurrentWorkspaceScreen();

		public abstract boolean isAllAppsVisible();

		public abstract void startBinding();
	}

	/**
	 */
	@Override
	public void onReceive(Context context, Intent intent) {
		// Log.d("Launcher", "onReceive");
		// if (this.mBeforeFirstLoad) {
		// return;
		// }
		String action = intent.getAction();
		Log.d("Launcher", "LauncherModel-onReceive=" + action);
		String packageName = intent.getData().getSchemeSpecificPart();
		boolean isReplacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING,
				false);
		final LauncherModel.Callbacks localCallbacks = (LauncherModel.Callbacks) mCallbacks
				.get();

		synchronized (this.mAllAppsListLock) {

			if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
				if (!isReplacing) {
					if (this.mAllAppsList.added.size() > 0) {
						this.mAllAppsList.added.clear();
					}
					this.mAllAppsList.addPackage(context, packageName);
					final ArrayList<ApplicationInfo> addedFinal = this.mAllAppsList.added;
					this.mHandler.post(new Runnable() {
						public void run() {
							localCallbacks.bindAppsAdded(addedFinal);
						}
					});
				}

			} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
				// Log.e(TAG,"ACTION_PACKAGE_REMOVED");
				if (!isReplacing) {

					for (ApplicationInfo localApplicationInfo : this.mAllAppsList.removed) {
						this.mIconCache.remove(localApplicationInfo.intent
								.getComponent());
					}
					this.mAllAppsList.removed.clear();
					this.mAllAppsList.removePackage(packageName);
					final ArrayList<ApplicationInfo> removedFinal = this.mAllAppsList.removed;
					this.mHandler.post(new Runnable() {
						public void run() {
							localCallbacks.bindAppsRemoved(removedFinal);
						}
					});
				}

			} else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
				if (this.mAllAppsList.modified.size() > 0) {
					this.mAllAppsList.modified.clear();
				}
				this.mAllAppsList.updatePackage(context, packageName);
				final ArrayList<ApplicationInfo> modifiedFinal = this.mAllAppsList.modified;
				this.mHandler.post(new Runnable() {
					public void run() {
						localCallbacks.bindAppsUpdated(modifiedFinal);

					}
				});
			}

		}
	}

	/**
	 * 
	 * @param context
	 * @return
	 */
	static Cursor loadAppFromDatabase(Context context) {
		LogUtils.d(TAG, "loadAppFromDatabase");
		return context.getContentResolver().query(
				LauncherSettings.Apps.CONTENT_URI, APPS_PROJECTION, null, null,
				null);
	}

	/**
	 * 
	 */
	public void stopLoader() {
		this.mLoader.stopLoader();
	}

	public class Loader {
		private static final int ITEMS_CHUNK = 6;
		final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
		final HashMap<Long, FolderInfo> mFolders;
		final ArrayList<ItemInfo> mItems;
		private LoaderThread mLoaderThread;

		/**
		 * 
		 */
		public Loader() {
			this.mItems = new ArrayList<ItemInfo>();
			this.mAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
			this.mFolders = new HashMap<Long, FolderInfo>();
		}

		/**
		 * 
		 * @param info
		 */
		public void addAppWidgetArray(LauncherAppWidgetInfo info) {
			this.mAppWidgets.add(info);
		}

		/**
		 * 
		 * @param info
		 */
		public void addFolderArray(FolderInfo info) {
			this.mFolders.put(info.id, info);
			this.mItems.add(info);
		}

		/**
		 * 
		 * @param info
		 */
		public void addItemArray(ItemInfo info) {
			this.mItems.add(info);
		}

		/**
		 * 
		 * @param paramLauncherAppWidgetInfo
		 */
		public void removeAppWidgetArray(LauncherAppWidgetInfo info) {
			Iterator<LauncherAppWidgetInfo> localIterator = this.mAppWidgets
					.iterator();
			while (localIterator.hasNext()) {
				LauncherAppWidgetInfo localLauncherAppWidgetInfo = localIterator
						.next();
				if (info.equals(localLauncherAppWidgetInfo)) {
					this.mAppWidgets.remove(localLauncherAppWidgetInfo);
					return;
				}
			}
		}

		/**
		 * 
		 * @param info
		 */
		public void removeFolderArray(FolderInfo info) {
			this.mFolders.remove(info.id);
			Iterator<ItemInfo> localIterator = this.mItems.iterator();
			while (localIterator.hasNext()) {
				ItemInfo localItemInfo = localIterator.next();
				if (info.equals(localItemInfo)) {
					this.mItems.remove(localItemInfo);
					return;
				}
			}
		}

		/**
		 * 
		 * @param info
		 */
		public void removeItemArray(ItemInfo info) {
			Iterator<ItemInfo> localIterator = this.mItems.iterator();
			while (localIterator.hasNext()) {
				ItemInfo localItemInfo = localIterator.next();
				if (info.equals(localItemInfo)) {
					this.mItems.remove(localItemInfo);
					return;
				}
			}
		}

		public void startLoader(Context context, boolean isLaunching) {

			synchronized (LauncherModel.this.mLock) {
				Log.d("Launcher", TAG + "Loader-startLoader isLaunching="
						+ isLaunching);

				if ((LauncherModel.this.mCallbacks != null)
						&& (LauncherModel.this.mCallbacks.get() != null)) {
					// boolean bool =
					// LauncherModel.access$202(LauncherModel.this, 1);
					if (this.mLoaderThread != null) {
						if (this.mLoaderThread.isLaunching()) {
							isLaunching = true;
						}
						this.mLoaderThread.stopLocked();
					}
					this.mLoaderThread = new LoaderThread(context,
							this.mLoaderThread, isLaunching);
					this.mLoaderThread.start();
				}
			}
		}

		/**
		 */
		public void stopLoader() {
			synchronized (LauncherModel.this.mLock) {
				if (this.mLoaderThread != null)
					this.mLoaderThread.stopLocked();
			}
		}

		class LoaderThread extends Thread {

			private Context mContext;
			private boolean mIsLaunching;
			private boolean mLoadAndBindStepFinished;
			private boolean mStopped;
			private Thread mWaitThread;

			/**
			 * 
			 * @param context
			 * @param thread
			 * @param isLaunching
			 */
			LoaderThread(Context context, Thread thread, boolean isLaunching) {
				this.mContext = context;
				this.mWaitThread = thread;
				this.mIsLaunching = isLaunching;
			}

			private void bindWorkspace() {
				final long t = SystemClock.uptimeMillis();

				final Callbacks oldCallbacks = (LauncherModel.Callbacks) mCallbacks
						.get();
				if (oldCallbacks == null) {
					return;
				}
				mHandler.post(new Runnable() {
					public void run() {
						Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.startBinding();
						}
					}
				});

				int N = mItems.size();
				for (int i = 0; i < N; i += ITEMS_CHUNK) {
					final int start = i;
					final int chunkSize = (i + ITEMS_CHUNK <= N) ? ITEMS_CHUNK
							: (N - i);
					mHandler.post(new Runnable() {
						public void run() {
							Callbacks callbacks = tryGetCallbacks(oldCallbacks);
							if (callbacks != null) {
								callbacks.bindItems(mItems, start, start
										+ chunkSize);
							}
						}
					});
				}

				mHandler.post(new Runnable() {
					public void run() {
						Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.bindFolders(mFolders);
						}
					}
				});

				// Wait until the queue goes empty.
				mHandler.post(new Runnable() {
					public void run() {
						if (DEBUG_LOADERS) {
							Log.d(TAG, "Going to start binding widgets soon.");
						}
					}
				});

				mHandler.post(new Runnable() {
					public void run() {
						Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.finishBindingShortcuts();
						}
					}
				});

				// Bind the widgets, one at a time.
				// WARNING: this is calling into the workspace from the
				// background thread,
				// but since getCurrentScreen() just returns the int, we should
				// be okay. This
				// is just a hint for the order, and if it's wrong, we'll be
				// okay.
				// TODO: instead, we should have that push the current screen
				// into here.
				final int currentScreen = oldCallbacks
						.getCurrentWorkspaceScreen();
				N = mAppWidgets.size();
				// once for the current screen
				for (int i = 0; i < N; i++) {
					final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
					if (widget.screen == currentScreen) {
						mHandler.post(new Runnable() {
							public void run() {
								Callbacks callbacks = tryGetCallbacks(oldCallbacks);
								if (callbacks != null) {
									callbacks.bindAppWidget(widget);
								}
							}
						});
					}
				}
				// once for the other screens
				for (int i = 0; i < N; i++) {
					final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
					if (widget.screen != currentScreen) {
						mHandler.post(new Runnable() {
							public void run() {
								Callbacks callbacks = tryGetCallbacks(oldCallbacks);
								if (callbacks != null) {
									callbacks.bindAppWidget(widget);
								}
							}
						});
					}
				}

				mHandler.post(new Runnable() {
					public void run() {
						Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.finishBindingAppWidgets();
						}
					}
				});

				mHandler.post(new Runnable() {
					public void run() {
						Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.finishBindingAllWorkspaceItems();
						}
					}
				});

				mHandler.post(new Runnable() {
					public void run() {
						if (DEBUG_LOADERS) {
							Log.d(TAG,
									"bound workspace in "
											+ (SystemClock.uptimeMillis() - t)
											+ "ms");
						}
					}
				});
			}

			// check & update map of what's occupied; used to discard
			// overlapping/invalid items
			private boolean checkItemPlacement(ItemInfo occupied[][][],
					ItemInfo item) {
				if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
					return true;
				}

				for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
					for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
						if (occupied[item.screen][x][y] != null) {
							Log.e(TAG, "Error loading shortcut " + item
									+ " into cell (" + item.screen + ":" + x
									+ "," + y + ") occupied by "
									+ occupied[item.screen][x][y]);
							return false;
						}
					}
				}
				for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
					for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
						occupied[item.screen][x][y] = item;
					}
				}
				return true;
			}

			private ApplicationInfo findApp(AllAppsList mAllAppsList,
					ComponentName componentName) {
				if (mAllAppsList == null || componentName == null) {
					return null;
				}
				int count = mAllAppsList.size();
				for (int i = 0; i < count; i++) {
					ApplicationInfo localApplicationInfo = mAllAppsList.get(i);
					ComponentName tmpComponentName = localApplicationInfo.intent
							.getComponent();
					if (componentName.equals(tmpComponentName)) {
						return localApplicationInfo;
					}
				}
				return null;
			}

			private boolean isAppExists(List<ResolveInfo> apps,
					String packageName) {
				if (null == apps || null == packageName) {
					return false;
				}
				Iterator<ResolveInfo> it = apps.iterator();
				try {
					while (it.hasNext()) {
						ResolveInfo info = it.next();
						if (info.activityInfo.packageName.equals(packageName))
							return true;
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				return false;

			}

			private void loadAllAppsByBatch() {
				// Log.e(TAG, "loadAllAppsByBatch");
				if (null != querySdCardAppRunnable) {
					osHandler.removeCallbacks(querySdCardAppRunnable);
				}

				final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

				// Don't use these two variables in any of the callback
				// runnables.
				// Otherwise we hold a reference to them.
				final Callbacks oldCallbacks = mCallbacks.get();
				if (oldCallbacks == null) {
					// This launcher has exited and nobody bothered to tell us.
					// Just bail.
					Log.w(TAG,
							"LoaderThread running with no launcher (loadAllAppsByBatch)");
					return;
				}

				final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
				mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

				final PackageManager packageManager = mContext
						.getPackageManager();
				List<ResolveInfo> apps = null;

				int N = Integer.MAX_VALUE;

				int startIndex;
				int i = 0;
				int batchSize = -1;

				Cursor localCursor = null;

				while (i < N && !mStopped) {
					synchronized (LauncherModel.this.mAllAppsListLock) {
						if (i == 0) {
							mAllAppsList.clear();
							final long qiaTime = DEBUG_LOADERS ? SystemClock
									.uptimeMillis() : 0;
							apps = packageManager.queryIntentActivities(
									mainIntent, 0);
							if (DEBUG_LOADERS) {
								Log.d(TAG,
										"queryIntentActivities took "
												+ (SystemClock.uptimeMillis() - qiaTime)
												+ "ms");
							}
							if (apps == null) {
								return;
							}
							N = apps.size();
							if (DEBUG_LOADERS) {
								Log.d(TAG, "queryIntentActivities got " + N
										+ " apps");
							}
							Log.e(TAG, "app  size  is " + N);

							if (N == 0) {
								// There are no apps?!?
								return;
							}
							if (mBatchSize == 0) {
								batchSize = N;
							} else {
								batchSize = mBatchSize;
							}
						}

						final long t2 = DEBUG_LOADERS ? SystemClock
								.uptimeMillis() : 0;

						startIndex = i;
						for (int j = 0; i < N && j < batchSize; j++) {
							// This builds the icon bitmaps.
							mAllAppsList.add(new ApplicationInfo(apps.get(i),
									mIconCache));
							i++;
						}

						localCursor = LauncherModel
								.loadAppFromDatabase(this.mContext);

						if (localCursor != null) {
							while (localCursor.moveToNext()) {
								// long id = localCursor.getLong(0);
								String strComponentName = localCursor
										.getString(1);
								ComponentName localComponentName = ComponentName
										.unflattenFromString(strComponentName);
								int topNum = localCursor.getInt(2);
								int pageNum = localCursor.getInt(3);
								int cellNum = localCursor.getInt(4);
								ApplicationInfo localApplicationInfo = this
										.findApp(mAllAppsList,
												localComponentName);
								if (localApplicationInfo != null) {
									localApplicationInfo.topNum = topNum;
									localApplicationInfo.pageNum = pageNum;
									localApplicationInfo.cellNum = cellNum;
									localApplicationInfo.editTopNum = topNum;
									localApplicationInfo.editPageNum = pageNum;
									localApplicationInfo.editCellNum = cellNum;
									localApplicationInfo.isUpdated = true;
									StringBuilder logAppInfo = new StringBuilder()
											.append("ApplicationInfo title=");
									logAppInfo.append(
											localApplicationInfo.title).append(
											" top=");
									logAppInfo.append(
											localApplicationInfo.topNum)
											.append(" page=");
									logAppInfo.append(
											localApplicationInfo.pageNum)
											.append(" cell=");
									logAppInfo
											.append(localApplicationInfo.cellNum);
									Log.d("Launcher", logAppInfo.toString());
								}
							}
							localCursor.close();
							localCursor = null;
						}

						final boolean first = i <= batchSize;
						final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						final ArrayList<ApplicationInfo> added = mAllAppsList.added;
						mAllAppsList.added = new ArrayList<ApplicationInfo>();

						mHandler.post(new Runnable() {
							public void run() {
								final long t = SystemClock.uptimeMillis();
								if (callbacks != null) {
									if (first) {
										callbacks.bindAllApplications(added);
									} else {
										callbacks.bindAppsAdded(added);
									}
									if (DEBUG_LOADERS) {
										Log.d(TAG,
												"bound "
														+ added.size()
														+ " apps in "
														+ (SystemClock
																.uptimeMillis() - t)
														+ "ms");
									}
								} else {
									Log.i(TAG,
											"not binding apps: no Launcher activity");
								}
							}
						});

						// osHandler.post(new Runnable() {
						// public void run() {
						// if
						// (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
						// {
						// //if (appList.size() == appSize) {
						// List<ResolveInfo> appList =
						// packageManager.queryIntentActivities(mainIntent, 0);
						// for (ResolveInfo resolveInfo : appList) {
						// if ((resolveInfo.activityInfo.applicationInfo.flags &
						// 262144) != 0) {
						// LauncherModel.this.mLauncher.addApplication(new
						// ApplicationInfo(resolveInfo, mIconCache));
						// }
						// }
						// osHandler.removeCallbacks(this);
						// } else {
						// osHandler.postDelayed(this, 1500);
						// }
						// }
						// });

						if (DEBUG_LOADERS) {
							Log.d(TAG,
									"batch of " + (i - startIndex)
											+ " icons processed in "
											+ (SystemClock.uptimeMillis() - t2)
											+ "ms");
						}

						if (mAllAppsLoadDelay > 0 && i < N) {
							try {
								if (DEBUG_LOADERS) {
									Log.d(TAG, "sleeping for "
											+ mAllAppsLoadDelay + "ms");
								}
								Thread.sleep(mAllAppsLoadDelay);
							} catch (InterruptedException exc) {
							}
						}

					}
				}

				final ArrayList<Long> toRemove = new ArrayList<Long>();
				final List<ResolveInfo> finalApps = apps;

				querySdCardAppRunnable = new Runnable() {
					public void run() {
						if (Environment.getExternalStorageState().equals(
								Environment.MEDIA_MOUNTED)) {
							List<ResolveInfo> appList = packageManager
									.queryIntentActivities(mainIntent, 0);
							for (ResolveInfo resolveInfo : appList) {
								if (!isAppExists(finalApps,
										resolveInfo.activityInfo.packageName)
										&& ((resolveInfo.activityInfo.applicationInfo.flags & 262144) != 0)) {
									LauncherModel.this.mLauncher
											.addApplication(new ApplicationInfo(
													resolveInfo, mIconCache));
								}
							}
							osHandler.removeCallbacks(this);
						} else {
							osHandler.postDelayed(this, 2000);
							querySdCardAppTimes++;
							if (querySdCardAppTimes >= 30) {
								osHandler.removeCallbacks(this);
								querySdCardAppTimes = 0;
							}
						}
					}
				};

				osHandler.post(querySdCardAppRunnable);

				final Cursor cursor = LauncherModel
						.loadAppFromDatabase(LoaderThread.this.mContext);

				// ɾ�����Ӧ��
				mHandler.post(new Runnable() {
					public void run() {
						if (!mStopped) {
							final long t = SystemClock.uptimeMillis();

							if (cursor != null) {
								while (cursor.moveToNext()) {
									long id = cursor.getLong(0);
									String strComponentName = cursor
											.getString(1);
									ComponentName localComponentName = ComponentName
											.unflattenFromString(strComponentName);

									String pkg = localComponentName
											.getPackageName();
									if (!isAppExists(finalApps, pkg)) {
										toRemove.add(id);
									}

								}
								cursor.close();
							}
							int dbMoreCount = toRemove.size();
							for (int i = 0; i < dbMoreCount; i++) {
								Long id = (Long) toRemove.get(i);
								LauncherModel.removeAppToDatabase(
										LoaderThread.this.mContext,
										id.toString());
							}
							final long t2 = SystemClock.uptimeMillis() - t;
							Log.e(TAG, "remove app  took  " + t2 + " ms");
						}
					}
				});

				if (DEBUG_LOADERS) {
					Log.d(TAG,
							"cached all "
									+ N
									+ " apps in "
									+ (SystemClock.uptimeMillis() - t)
									+ "ms"
									+ (mAllAppsLoadDelay > 0 ? " (including delay)"
											: ""));
				}
			}

			/**
			 * ����
			 */
			private void loadAndBindAllApps() {
				if (DEBUG_LOADERS) {
					Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded="
							+ mAllAppsLoaded);
				}
				if (!mAllAppsLoaded) {
					loadAllAppsByBatch();
					if (mStopped) {
						return;
					}
					mAllAppsLoaded = true;
				} else {
					onlyBindAllApps();
				}
			}

			/**
			 * ����.
			 */
			private void loadAndBindWorkspace() {
				Log.d("Launcher", "loadAndBindWorkspace loaded="
						+ mWorkspaceLoaded);

				synchronized (LauncherModel.this.mLock) {
					if (!mWorkspaceLoaded) {
						loadWorkspace();
						if (this.mStopped) {
							// boolean bool3 =
							// LauncherModel.access$302(LauncherModel.this, 0);
							return;
						}
						mWorkspaceLoaded = true;
					}
				}
				bindWorkspace();
			}

			/**
			 * ����.
			 */
			private void loadWorkspace() {
				final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

				final Context context = mContext;
				final ContentResolver contentResolver = context
						.getContentResolver();
				final PackageManager manager = context.getPackageManager();
				final AppWidgetManager widgets = AppWidgetManager
						.getInstance(context);
				final boolean isSafeMode = manager.isSafeMode();

				mItems.clear();
				mAppWidgets.clear();
				mFolders.clear();

				final ArrayList<Long> itemsToRemove = new ArrayList<Long>();

				final Cursor c = contentResolver.query(
						LauncherSettings.Favorites.CONTENT_URI, null, null,
						null, null);

				final ItemInfo occupied[][][] = new ItemInfo[Launcher.SCREEN_COUNT][Launcher.NUMBER_CELLS_X][Launcher.NUMBER_CELLS_Y];

				try {
					final int idIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
					final int intentIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
					final int titleIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
					final int iconTypeIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
					final int iconIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
					final int iconPackageIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE);
					final int iconResourceIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE);
					final int containerIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
					final int itemTypeIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
					final int appWidgetIdIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWIDGET_ID);
					final int screenIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
					final int cellXIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
					final int cellYIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
					final int spanXIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
					final int spanYIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
					final int uriIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
					final int displayModeIndex = c
							.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);

					ShortcutInfo info;
					String intentDescription;
					Widget widgetInfo;
					LauncherAppWidgetInfo appWidgetInfo;
					int container;
					long id;
					Intent intent;

					while (!mStopped && c.moveToNext()) {
						try {
							int itemType = c.getInt(itemTypeIndex);

							switch (itemType) {
							case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
							case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
								intentDescription = c.getString(intentIndex);
								try {
									intent = Intent.parseUri(intentDescription,
											0);
								} catch (URISyntaxException e) {
									continue;
								}

								if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
									info = getShortcutInfo(manager, intent,
											context, c, iconIndex, titleIndex);
								} else {
									info = getShortcutInfo(c, context,
											iconTypeIndex, iconPackageIndex,
											iconResourceIndex, iconIndex,
											titleIndex);
								}

								if (info != null) {
									updateSavedIcon(context, info, c, iconIndex);

									info.intent = intent;
									info.id = c.getLong(idIndex);
									container = c.getInt(containerIndex);
									info.container = container;
									info.screen = c.getInt(screenIndex);
									info.cellX = c.getInt(cellXIndex);
									info.cellY = c.getInt(cellYIndex);

									// check & update map of what's occupied
									if (!checkItemPlacement(occupied, info)) {
										break;
									}

									switch (container) {
									case LauncherSettings.Favorites.CONTAINER_DESKTOP:
										mItems.add(info);
										break;
									default:
										// Item is in a user folder
										UserFolderInfo folderInfo = findOrMakeUserFolder(
												mFolders, container);
										folderInfo.add(info);
										break;
									}
								} else {
									// Failed to load the shortcut, probably
									// because the
									// activity manager couldn't resolve it
									// (maybe the app
									// was uninstalled), or the db row was
									// somehow screwed up.
									// Delete it.
									id = c.getLong(idIndex);
									Log.e(TAG, "Error loading shortcut " + id
											+ ", removing it");
									contentResolver.delete(
											LauncherSettings.Favorites
													.getContentUri(id, false),
											null, null);
								}
								break;

							case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
								id = c.getLong(idIndex);
								UserFolderInfo folderInfo = findOrMakeUserFolder(
										mFolders, id);

								folderInfo.title = c.getString(titleIndex);

								folderInfo.id = id;
								container = c.getInt(containerIndex);
								folderInfo.container = container;
								folderInfo.screen = c.getInt(screenIndex);
								folderInfo.cellX = c.getInt(cellXIndex);
								folderInfo.cellY = c.getInt(cellYIndex);

								// check & update map of what's occupied
								if (!checkItemPlacement(occupied, folderInfo)) {
									break;
								}

								switch (container) {
								case LauncherSettings.Favorites.CONTAINER_DESKTOP:
									mItems.add(folderInfo);
									break;
								}

								mFolders.put(folderInfo.id, folderInfo);
								break;

							case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
								id = c.getLong(idIndex);
								Uri uri = Uri.parse(c.getString(uriIndex));

								// Make sure the live folder exists
								final ProviderInfo providerInfo = context
										.getPackageManager()
										.resolveContentProvider(
												uri.getAuthority(), 0);

								if (providerInfo == null && !isSafeMode) {
									itemsToRemove.add(id);
								} else {
									LiveFolderInfo liveFolderInfo = findOrMakeLiveFolder(
											mFolders, id);

									intentDescription = c
											.getString(intentIndex);
									intent = null;
									if (intentDescription != null) {
										try {
											intent = Intent.parseUri(
													intentDescription, 0);
										} catch (URISyntaxException e) {
											// Ignore, a live folder might not
											// have a base intent
										}
									}

									liveFolderInfo.title = c
											.getString(titleIndex);
									liveFolderInfo.id = id;
									liveFolderInfo.uri = uri;
									container = c.getInt(containerIndex);
									liveFolderInfo.container = container;
									liveFolderInfo.screen = c
											.getInt(screenIndex);
									liveFolderInfo.cellX = c.getInt(cellXIndex);
									liveFolderInfo.cellY = c.getInt(cellYIndex);
									liveFolderInfo.baseIntent = intent;
									liveFolderInfo.displayMode = c
											.getInt(displayModeIndex);

									// check & update map of what's occupied
									if (!checkItemPlacement(occupied,
											liveFolderInfo)) {
										break;
									}

									loadLiveFolderIcon(context, c,
											iconTypeIndex, iconPackageIndex,
											iconResourceIndex, liveFolderInfo);

									switch (container) {
									case LauncherSettings.Favorites.CONTAINER_DESKTOP:
										mItems.add(liveFolderInfo);
										break;
									}
									mFolders.put(liveFolderInfo.id,
											liveFolderInfo);
								}
								break;

							case LauncherSettings.Favorites.ITEM_TYPE_WIDGET_SEARCH: {
								widgetInfo = Widget.makeSearch();
								container = c.getInt(containerIndex);
								if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
									Log.e(Launcher.TAG,
											"Widget found where container "
													+ "!= CONTAINER_DESKTOP  ignoring!");
									continue;
								}
								widgetInfo.id = c.getLong(idIndex);
								widgetInfo.screen = c.getInt(screenIndex);
								widgetInfo.container = container;
								widgetInfo.cellX = c.getInt(cellXIndex);
								widgetInfo.cellY = c.getInt(cellYIndex);
								mItems.add(widgetInfo);
								break;
							}

							case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
								// Read all Launcher-specific widget details
								int appWidgetId = c.getInt(appWidgetIdIndex);
								id = c.getLong(idIndex);

								final AppWidgetProviderInfo provider = widgets
										.getAppWidgetInfo(appWidgetId);

								if (!isSafeMode
										&& (provider == null
												|| provider.provider == null || provider.provider
												.getPackageName() == null)) {
									Log.e(TAG,
											"Deleting widget that isn't installed anymore: id="
													+ id + " appWidgetId="
													+ appWidgetId);
									itemsToRemove.add(id);
								} else {
									appWidgetInfo = new LauncherAppWidgetInfo(
											appWidgetId);
									appWidgetInfo.id = id;
									appWidgetInfo.screen = c
											.getInt(screenIndex);
									appWidgetInfo.cellX = c.getInt(cellXIndex);
									appWidgetInfo.cellY = c.getInt(cellYIndex);
									appWidgetInfo.spanX = c.getInt(spanXIndex);
									appWidgetInfo.spanY = c.getInt(spanYIndex);

									container = c.getInt(containerIndex);
									if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
										Log.e(TAG,
												"Widget found where container "
														+ "!= CONTAINER_DESKTOP -- ignoring!");
										continue;
									}
									appWidgetInfo.container = c
											.getInt(containerIndex);

									// check & update map of what's occupied
									if (!checkItemPlacement(occupied,
											appWidgetInfo)) {
										break;
									}

									mAppWidgets.add(appWidgetInfo);
								}
								break;
							}
						} catch (Exception e) {
							Log.w(TAG, "Desktop items loading interrupted:", e);
						}
					}
				} finally {
					c.close();
				}

				if (itemsToRemove.size() > 0) {
					ContentProviderClient client = contentResolver
							.acquireContentProviderClient(LauncherSettings.Favorites.CONTENT_URI);
					// Remove dead items
					for (long id : itemsToRemove) {
						if (DEBUG_LOADERS) {
							Log.d(TAG, "Removed id = " + id);
						}
						// Don't notify content observers
						try {
							client.delete(LauncherSettings.Favorites
									.getContentUri(id, false), null, null);
						} catch (RemoteException e) {
							Log.w(TAG, "Could not remove id = " + id);
						}
					}
				}

				if (DEBUG_LOADERS) {
					Log.d(TAG,
							"loaded workspace in "
									+ (SystemClock.uptimeMillis() - t) + "ms");
					Log.d(TAG, "workspace layout: ");
					for (int y = 0; y < Launcher.NUMBER_CELLS_Y; y++) {
						String line = "";
						for (int s = 0; s < Launcher.SCREEN_COUNT; s++) {
							if (s > 0) {
								line += " | ";
							}
							for (int x = 0; x < Launcher.NUMBER_CELLS_X; x++) {
								line += ((occupied[s][x][y] != null) ? "#"
										: ".");
							}
						}
						Log.d(TAG, "[ " + line + " ]");
					}
				}
			}

			/**
			 * ����
			 */
			private void onlyBindAllApps() {
				final Callbacks oldCallbacks = mCallbacks.get();
				if (oldCallbacks == null) {
					Log.w(TAG,
							"LoaderTask running with no launcher (onlyBindAllApps)");
					return;
				}
				// shallow copy
				final ArrayList<ApplicationInfo> list = (ArrayList<ApplicationInfo>) mAllAppsList.data
						.clone();
				mHandler.post(new Runnable() {
					public void run() {
						final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
						if (callbacks != null) {
							callbacks.bindAllApplications(list);
						}
					}
				});
			}

			/**
			 * ����
			 */
			private void waitForIdle() {
				// Wait until the either we're stopped or the other threads are
				// done.
				// This way we don't start loading all apps until the workspace
				// has settled
				// down.
				synchronized (LoaderThread.this) {
					final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock
							.uptimeMillis() : 0;

					mHandler.postIdle(new Runnable() {
						public void run() {
							synchronized (LoaderThread.this) {
								mLoadAndBindStepFinished = true;
								if (DEBUG_LOADERS) {
									Log.d(TAG,
											"done with previous binding step");
								}
								LoaderThread.this.notify();
							}
						}
					});

					while (!mStopped && !mLoadAndBindStepFinished) {
						try {
							this.wait();
						} catch (InterruptedException ex) {
							// Ignore
						}
					}
					if (DEBUG_LOADERS) {
						Log.d(TAG,
								"waited "
										+ (SystemClock.uptimeMillis() - workspaceWaitTime)
										+ "ms for previous step to finish binding");
					}
				}
			}

			/**
			 * 
			 */
			private void waitForOtherThread() {
				if (this.mWaitThread != null) {
					try {
						this.mWaitThread.join();
						this.mWaitThread = null;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

			/**
			 * 
			 */
			public void dumpState() {
				Log.d(TAG, "mLoaderTask.mContext=" + mContext);
				Log.d(TAG, "mLoaderTask.mWaitThread=" + mWaitThread);
				Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching);
				Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
				Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished="
						+ mLoadAndBindStepFinished);

			}

			boolean isLaunching() {
				return this.mIsLaunching;
			}

			public void run() {
				// Optimize for end-user experience: if the Launcher is up and
				// // running with the
				// All Apps interface in the foreground, load All Apps first.
				// Otherwise, load the
				// workspace first (default).
				final Callbacks cbk = mCallbacks.get();
				final boolean loadWorkspaceFirst = cbk != null ? (!cbk
						.isAllAppsVisible()) : true;

				keep_running: {
					// Elevate priority when Home launches for the first time to
					// avoid
					// starving at boot time. Staring at a blank home is not
					// cool.
					synchronized (mLock) {
						android.os.Process
								.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT
										: Process.THREAD_PRIORITY_BACKGROUND);
					}

					if (loadWorkspaceFirst) {
						if (DEBUG_LOADERS)
							Log.d(TAG, "step 1: loading workspace");
						loadAndBindWorkspace();
					} else {
						if (DEBUG_LOADERS)
							Log.d(TAG, "step 1: special: loading all apps");
						loadAndBindAllApps();
					}

					if (mStopped) {
						break keep_running;
					}

					// Whew! Hard work done. Slow us down, and wait until the UI
					// thread has
					// settled down.
					synchronized (mLock) {
						if (mIsLaunching) {
							android.os.Process
									.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
						}
					}
					waitForIdle();

					// second step
					if (loadWorkspaceFirst) {
						if (DEBUG_LOADERS)
							Log.d(TAG, "step 2: loading all apps");
						loadAndBindAllApps();
					} else {
						if (DEBUG_LOADERS)
							Log.d(TAG, "step 2: special: loading workspace");
						loadAndBindWorkspace();
					}
				}

				// Clear out this reference, otherwise we end up holding it
				// until all of the
				// callback runnables are done.
				mContext = null;

				synchronized (mLock) {
					// If we are still the last one to be scheduled, remove
					// ourselves.
					if (mLoaderThread == this) {
						mLoaderThread = null;
					}
				}

				// Trigger a gc to try to clean up after the stuff is done,
				// since the
				// renderscript allocations aren't charged to the java heap.
				if (mStopped) {
					mHandler.post(new Runnable() {
						public void run() {
							System.gc();
						}
					});
				} else {
					mHandler.postIdle(new Runnable() {
						public void run() {
							System.gc();
						}
					});
				}
			}

			public void stopLocked() {
				synchronized (LoaderThread.this) {
					mStopped = true;
					this.notify();
				}
			}

			LauncherModel.Callbacks tryGetCallbacks() {
				synchronized (LauncherModel.this.mLock) {
					if (this.mStopped) {
						return null;
					}

					if (mCallbacks != null) {
						LauncherModel.Callbacks localCallbacks = (LauncherModel.Callbacks) mCallbacks
								.get();
						if (localCallbacks != null) {
							return localCallbacks;
						}
					}

					return null;
				}
			}

			LauncherModel.Callbacks tryGetCallbacks(
					LauncherModel.Callbacks oldCallbacks) {
				synchronized (mLock) {
					if (mStopped) {
						return null;
					}

					if (mCallbacks == null) {
						return null;
					}
					final Callbacks callbacks = mCallbacks.get();
					if (callbacks != oldCallbacks) {
						return null;
					}
					if (callbacks == null) {
						Log.w(TAG, "no mCallbacks");
						return null;
					}
					return callbacks;
				}
			}

		}// LoaderThread
	}// Loader
}
